Skip to content

Conversation

@Devasy
Copy link
Owner

@Devasy Devasy commented Feb 10, 2026

Implemented a skeleton loading state for the mobile Home Screen to improve the perceived performance and user experience.

Features:

  • Created a reusable Skeleton primitive component in mobile/components/ui/Skeleton.js that supports pulsing animations and theme adaptation.
  • Created GroupListSkeleton in mobile/components/skeletons/GroupListSkeleton.js that mimics the layout of the group list cards.
  • Replaced the generic ActivityIndicator in mobile/screens/HomeScreen.js with the new skeleton loading state.
  • Ensured accessibility by adding proper roles and labels to the skeleton container.

Changes:

  • mobile/components/ui/Skeleton.js: New component.
  • mobile/components/skeletons/GroupListSkeleton.js: New component.
  • mobile/screens/HomeScreen.js: Replaced loader with skeleton, removed unused styles.
  • .Jules/todo.md: Marked task as complete.
  • .Jules/changelog.md: Added entry for the new feature.

PR created automatically by Jules for task 3504540643638496533 started by @Devasy23

Summary by CodeRabbit

  • New Features
    • Implemented skeleton loading placeholders with pulsing animation for group lists on the home screen. This replaces the previous loading spinner, enhancing perceived performance and minimizing visual layout shifts during data loading.

Co-authored-by: Devasy23 <110348311+Devasy23@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@Devasy Devasy requested a review from vrajpatelll as a code owner February 10, 2026 19:45
@netlify
Copy link

netlify bot commented Feb 10, 2026

Deploy Preview for split-but-wiser canceled.

Name Link
🔨 Latest commit 589f19f
🔍 Latest deploy log https://app.netlify.com/projects/split-but-wiser/deploys/698b8af8f4474c0008089ef6

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

Walkthrough

Implements mobile skeleton loading UI for the HomeScreen group list. Creates a reusable Skeleton component with pulsing animation and a GroupListSkeleton component that renders placeholder cards. Replaces the ActivityIndicator loading indicator with the skeleton placeholder. Updates documentation to reflect completion.

Changes

Cohort / File(s) Summary
Documentation Updates
.Jules/changelog.md, .Jules/todo.md
Changelog entry added for mobile skeleton loading implementation; todo task marked complete with timestamp.
Skeleton UI Components
mobile/components/ui/Skeleton.js, mobile/components/skeletons/GroupListSkeleton.js
Reusable Skeleton component with infinite pulsing animation and opacity transitions; GroupListSkeleton renders five stacked Card placeholders with skeleton elements and accessibility labels.
HomeScreen Integration
mobile/screens/HomeScreen.js
Replaced ActivityIndicator with GroupListSkeleton during loading state; removed associated loader container styling.

Possibly related PRs

  • [Mobile] Complete Accessibility Audit #269: Adds accessibilityRole and related a11y properties to skeleton/loading containers across mobile screens, aligning with the accessibility enhancements introduced in this PR's Skeleton component.

Suggested reviewers

  • vrajpatelll
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing mobile skeleton loading for the Home Screen groups list, which is the primary focus of this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@mobile/components/skeletons/GroupListSkeleton.js`:
- Around line 14-30: Update the static Skeleton dimensions in the
GroupListSkeleton component so each Card renders varied widths: define small
arrays like titleWidths = [120,150,100,130,110] and subtitleWidths =
[200,160,180,140,170] and pick values by index inside the map that renders Card
and Skeleton (references: the map over [...Array(5)], Card.Title, Card.Content,
and Skeleton). Replace the hardcoded numeric props (title: 120, subtitle: 200)
with titleWidths[index] and subtitleWidths[index] (or a simple deterministic
alternation) so the skeletons vary while keeping layout stable.

In `@mobile/components/ui/Skeleton.js`:
- Around line 9-24: The Animated.loop started in the useEffect (which sequences
Animated.timing on opacity) isn’t stopped on unmount; capture the loop
instance/animation returned by Animated.loop(...) (or the value returned by
.start()) and return a cleanup function from useEffect that calls .stop() on
that instance so the looping animation is properly stopped when the component
unmounts; reference the useEffect, Animated.loop, opacity, .start(), and .stop()
in your change.

Comment on lines +14 to +30
{[...Array(5)].map((_, index) => (
<Card key={index} style={styles.card}>
<Card.Title
title={<Skeleton width={120} height={20} />}
left={(props) => (
<Skeleton
width={props.size}
height={props.size}
borderRadius={props.size / 2}
/>
)}
/>
<Card.Content>
<Skeleton width={200} height={16} style={styles.subtitle} />
</Card.Content>
</Card>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider varying skeleton widths for a more natural look.

All five cards render identical skeleton dimensions (title: 120, subtitle: 200). Slightly randomizing or alternating widths (e.g., [120, 150, 100, 130, 110]) would produce a more realistic shimmer effect and avoid a rigid, repetitive appearance.

🤖 Prompt for AI Agents
In `@mobile/components/skeletons/GroupListSkeleton.js` around lines 14 - 30,
Update the static Skeleton dimensions in the GroupListSkeleton component so each
Card renders varied widths: define small arrays like titleWidths =
[120,150,100,130,110] and subtitleWidths = [200,160,180,140,170] and pick values
by index inside the map that renders Card and Skeleton (references: the map over
[...Array(5)], Card.Title, Card.Content, and Skeleton). Replace the hardcoded
numeric props (title: 120, subtitle: 200) with titleWidths[index] and
subtitleWidths[index] (or a simple deterministic alternation) so the skeletons
vary while keeping layout stable.

Comment on lines +9 to +24
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0.5,
duration: 1000,
useNativeDriver: true,
}),
])
).start();
}, [opacity]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing animation cleanup on unmount.

The looping animation is never stopped when the component unmounts, which can cause a memory leak and "update on unmounted component" warnings. Store the return value of Animated.loop(…).start() — or the loop reference — and call .stop() in the effect cleanup.

🛡️ Proposed fix
  useEffect(() => {
-   Animated.loop(
+   const animation = Animated.loop(
      Animated.sequence([
        Animated.timing(opacity, {
          toValue: 1,
          duration: 1000,
          useNativeDriver: true,
        }),
        Animated.timing(opacity, {
          toValue: 0.5,
          duration: 1000,
          useNativeDriver: true,
        }),
      ])
-   ).start();
+   );
+   animation.start();
+   return () => animation.stop();
  }, [opacity]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0.5,
duration: 1000,
useNativeDriver: true,
}),
])
).start();
}, [opacity]);
useEffect(() => {
const animation = Animated.loop(
Animated.sequence([
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 0.5,
duration: 1000,
useNativeDriver: true,
}),
])
);
animation.start();
return () => animation.stop();
}, [opacity]);
🤖 Prompt for AI Agents
In `@mobile/components/ui/Skeleton.js` around lines 9 - 24, The Animated.loop
started in the useEffect (which sequences Animated.timing on opacity) isn’t
stopped on unmount; capture the loop instance/animation returned by
Animated.loop(...) (or the value returned by .start()) and return a cleanup
function from useEffect that calls .stop() on that instance so the looping
animation is properly stopped when the component unmounts; reference the
useEffect, Animated.loop, opacity, .start(), and .stop() in your change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant